from bcat.gtp.serde.error import UnexpectEndOfStreamError
from bcat.gtp.serde.dev2.ireader import IReader

class BytesReader(IReader):
    @staticmethod 
    def from_bytes(in_bytes):
        return BytesReader(in_bytes)
    @staticmethod
    def from_str(s, codec='utf-8'):
        return BytesReader(s.encode(codec))

    def __init__(self, gtp_bytes):
        self.__bytes = gtp_bytes if gtp_bytes is not None else bytes()
        self.__current = 0

    def __str__(self):
        return "len{},cur:{},available:{},bytes:{}".format(self.len(), self.position(), self.available(), self.__bytes)

    def get(self, index):
        return self.__bytes[index]

    #same usage as slice operation [start:end:step]
    def get_range(self, start, end=None, step=1):
        return self.__bytes[start:end:step]

    #TODO: unittest 
    def peek_or_raise(self):
        if self.__current >= 0 and self.__current < self.len():
            return self.__bytes[self.__current]
        else:
            msg = "peek from {}, valid:[{},{})".format(self.position(), self.position(), self.len())
            raise UnexpectEndOfStreamError(self.position(), msg)
        
    def peek(self) -> int | None:
        if self.__current >= 0 and self.__current < self.len():
            return self.__bytes[self.__current]
        else:
            return None

    def peekn(self, n=1):
        if n > 0 and n <= self.len() - self.__current:
            return self.__bytes[self.__current: self.__current+n]
        else:
            #current, n, len
            msg = "peekn({}) from [{},{}), valid:[{}:{})".format(n, self.position(), self.position() + n, self.position(), self.len())
            raise UnexpectEndOfStreamError(self.position(), msg)


    def eat(self):
        if self.available() > 0:
            self.__current = self.__current + 1
        else:
            msg = "eat from {}, valid:[{}:{})".format(self.position(), self.position(), self.len())
            raise UnexpectEndOfStreamError(self.position(), msg)

    def eatn(self, n=1):
        if n > 0 and n <= self.len() - self.__current:
            self.__current = self.__current + n
        else:
            msg = "eatn({}) from [{},{}),valid:[{}:{})".format(n, self.position(), self.position()+n, self.position(), self.len())
            raise UnexpectEndOfStreamError(self.position(), msg)


    def next(self) -> int | None:
        if self.available() > 0:
            curr_val = self.__bytes[self.__current]
            self.__current = self.__current + 1
            return curr_val
        else:
            return None
            #msg = "next from {}, valid:[{}:{})".format(self.position(), self.position(), self.len())
            #raise UnexpectEndOfStreamError(self.position(), msg)

    #TODO: unittest 
    def next_or_raise(self):
        if self.available() > 0:
            curr_val = self.__bytes[self.__current]
            self.__current = self.__current + 1
            return curr_val
        else:
            msg = "next from {}, valid:[{}:{})".format(self.position(), self.position(), self.len())
            raise UnexpectEndOfStreamError(self.position(), msg)

    def position(self):
        return  self.__current

    def available(self):
        return  self.len() - self.__current

    def is_end(self):
        return self.__current == self.len()

    def len(self):
        return len(self.__bytes)

import unittest
from bcat.util.str import Str
class TestBytesReader(unittest.TestCase):
    def test_main(self):
        bytes_str = 'X03=A100,M66=abc123'
        reader = BytesReader.from_str(bytes_str, "gbk")

        self.assertEqual(ord('X'), reader.peek())
        self.assertEqual(0, reader.position())
        self.assertEqual(reader.len(), reader.available())

        self.assertEqual(b'X0', reader.get_range(0, 2))

        self.assertEqual(ord('X'), reader.next())
        self.assertEqual(1, reader.position())

        self.assertEqual(b'03', reader.peekn(2))
        self.assertEqual(1, reader.position())

        reader.eat()
        self.assertEqual(2, reader.position())

        reader.eatn(reader.available())
        self.assertTrue(reader.is_end())


    def test_get(self):
        in_bytes = b"0123"
        reader = BytesReader.from_bytes(in_bytes)
        self.assertEqual(4, reader.available())

        self.assertEqual(ord('0'), reader.get(0))
        self.assertEqual(ord('1'), reader.get(1))
        self.assertEqual(ord('2'), reader.get(2))
        self.assertEqual(ord('3'), reader.get(3))
        self.assertEqual(ord('3'), reader.get(-1))

        self.assertRaises(IndexError, reader.get, *(4,))

    def test_get_range(self):
        in_bytes = b"0123"
        reader = BytesReader.from_bytes(in_bytes)
        self.assertEqual(b'0', reader.get_range(0, 1, 1))
        self.assertEqual(b'0', reader.get_range(0, 1))
        self.assertEqual(b'01', reader.get_range(0, 2, 1))
        self.assertEqual(b'01', reader.get_range(0, 2))

        self.assertEqual(b'012', reader.get_range(0, -1))
        self.assertEqual(in_bytes, reader.get_range(0, None))
        self.assertEqual(in_bytes, reader.get_range(0))
        self.assertEqual(in_bytes, reader.get_range(0, 4))

    def test_peek(self):
        in_bytes = b'0123'
        reader = BytesReader.from_bytes(in_bytes)
        self.assertEqual(ord('0'), reader.peek())
    
    def test_peek_meet_end(self):
        reader = BytesReader(b'')
        self.assertIsNone(reader.peek())
        
    def test_peek_after_eat_last_byte(self):
        reader = BytesReader.from_bytes(b'0')
        self.assertEqual(1, reader.available())

        reader.eat()
        self.assertEqual(0, reader.available())

        self.assertIsNone(reader.peek())

    def test_peekn(self):
        reader = BytesReader.from_bytes(b'0123')
        self.assertEqual(b'0', reader.peekn(1))
        self.assertEqual(b'01', reader.peekn(2))
        self.assertEqual(b'012', reader.peekn(3))
        self.assertEqual(b'0123', reader.peekn(4))

        self.assertRaises(UnexpectEndOfStreamError, reader.peekn, *(5,))
        self.assertEqual(4, reader.available())

        reader = BytesReader.from_bytes(b'123')
        self.assertEqual(b'1', reader.peekn(1))
        self.assertEqual(b'12', reader.peekn(2))
        self.assertEqual(b'123', reader.peekn(3))
        self.assertRaises(UnexpectEndOfStreamError, reader.peekn, *(4,))
        self.assertEqual(3, reader.available())

        reader = BytesReader.from_bytes(b'23')
        self.assertEqual(b'2', reader.peekn(1))
        self.assertEqual(b'23', reader.peekn(2))
        self.assertRaises(UnexpectEndOfStreamError, reader.peekn, *(3,))
        self.assertEqual(2, reader.available())

        reader = BytesReader.from_bytes(b'3')
        self.assertEqual(b'3', reader.peekn(1))
        self.assertRaises(UnexpectEndOfStreamError, reader.peekn, *(2,))

        self.assertEqual(1, reader.available())

    def test_peekn_with_less_avaiable_bytes(self):
        reader = BytesReader.from_bytes(b'')
        self.assertRaises(UnexpectEndOfStreamError, reader.peekn, *(1,))

    def test_eat(self):
        reader = BytesReader.from_bytes(b'0')
        self.assertEqual(1, reader.available())
        reader.eat()
        self.assertEqual(0, reader.available())

        self.assertRaises(UnexpectEndOfStreamError, reader.eat)

    def test_eat_out_of_range(self):
        reader = BytesReader.from_bytes(b'')
        self.assertRaises(UnexpectEndOfStreamError, reader.eat)

    def test_eatn(self):
        reader = BytesReader.from_bytes(b'0123')
        self.assertEqual(4, reader.available())
        reader.eatn(4)
        self.assertEqual(0, reader.available())

        #eatn out of range
        self.assertRaises(UnexpectEndOfStreamError, reader.eatn, *(1,))

    def test_eatn_out_of_range(self):
        reader = BytesReader.from_bytes(b'')
        self.assertRaises(UnexpectEndOfStreamError, reader.eatn, *(1,))

    def test_next(self):
        reader = BytesReader.from_bytes(b'0123')
        self.assertEqual(4, reader.available())
        self.assertEqual(ord('0'), reader.next())
        self.assertEqual(ord('1'), reader.next())
        self.assertEqual(ord('2'), reader.next())
        self.assertEqual(ord('3'), reader.next())
        
        self.assertIsNone(reader.next())

    def test_next_meet_end(self):
        reader = BytesReader.from_bytes(b'')
        self.assertIsNone(reader.next())
